#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 8080
#define BUFFER_SIZE 1024

void handle_client(int client_socket) {
    char buffer[BUFFER_SIZE];
    ssize_t bytes_read;

    // Send the initial "Knock knock" message to the client.
    const char *initial_message = "Knock knock\n";
    write(client_socket, initial_message, strlen(initial_message));

    while (1) {
        // Read a response from the client.
        bytes_read = read(client_socket, buffer, BUFFER_SIZE - 1);
        if (bytes_read <= 0) break; // Connection closed or error.

        buffer[bytes_read] = '\0'; // Null-terminate the string.

        // Echo back the client's message.
        write(client_socket, buffer, bytes_read);

        // Check for end of conversation.
        if (strncmp(buffer, "Who's there", 11) == 0 || strncmp(buffer, "who's there", 11) == 0) {
            const char *punchline = "Orange.\n";
            write(client_socket, punchline, strlen(punchline));
            break;
        }
    }

    close(client_socket);
}

int main() {
    int server_socket, client_socket;
    struct sockaddr_in server_addr, client_addr;
    socklen_t addr_size;

    // Create a socket.
    server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // Prepare the sockaddr_in structure.
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(PORT);

    // Bind the socket to the specified port.
    if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("bind");
        close(server_socket);
        exit(EXIT_FAILURE);
    }

    // Listen for incoming connections.
    if (listen(server_socket, 3) == -1) {
        perror("listen");
        close(server_socket);
        exit(EXIT_FAILURE);
    }

    printf("Server listening on port %d...\n", PORT);

    // Accept and handle incoming connections.
    while (1) {
        addr_size = sizeof(client_addr);
        client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &addr_size);
        if (client_socket == -1) {
            perror("accept");
            continue;
        }

        // Handle the client in a new process.
        pid_t pid = fork();
        if (pid == -1) {
            perror("fork");
            close(client_socket);
            continue;
        } else if (pid == 0) { // Child process
            close(server_socket); // Close the listening socket in the child.
            handle_client(client_socket);
            _exit(EXIT_SUCCESS);
        } else { // Parent process
            close(client_socket); // Close the connected socket in the parent.
        }
    }

    close(server_socket);
    return 0;
}
